package cn.edu.dgut.sai;

import org.apache.commons.lang3.exception.ExceptionUtils;

import javax.servlet.AsyncContext;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 *
 * @author Sai
 *
 * Java EE课程示例
 *
 * 利用curl的--limit-rate选项来模拟慢速上传:
 * curl -X POST -F "bigfile=@src/main/resources/bigfile" --limit-rate 5k http://localhost:8080/test/asyncIORead -v
 *
 * 每次处理事件的线程是从容器http线程池分发的线程；处理数据的时候，由自定义的线程池分发线程去处理。
 */
@WebServlet(value = "/asyncIORead", asyncSupported = true)
public class AsyncIOReadServlet extends HttpServlet {

  // 线程池是共享资源，用volatile修饰，保证一定的“有序性”，轻量级的同步
  private volatile ExecutorService executorService;

  // servlet被加载时，实例化一个线程池。
  @Override
  public void init() throws ServletException {
    super.init();
    // 实例化一个线程池，400线程数。
    executorService = Executors.newFixedThreadPool(400);
  }

  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {

    System.out.println("异步IO读取Servlet执行开始...");
    long start = System.currentTimeMillis();
    System.out.println("进入Servlet线程--" + Thread.currentThread().getName());

    AsyncContext asyncContext = request.startAsync();
    ServletInputStream is = request.getInputStream();

    // 添加一个读取事件监听器
    /*
      onDataAvailable() 将会在数据没有阻塞,，已经完全准备好可以读取的时候调用;
      servletInputStream.isReady() 方法用来检查数据是否已经准备好可被无阻塞读取。
      ServletInputStream.isFinished 方法可以用于检查非阻塞读取的状态。
      AsyncContext.complete() 方法在 onAllDataRead 和 onError 方法中调用，用来通知 servlet 上下文环境数据读取操作已经完成。
      onAllDataRead() 将会在所有数据读取完成后调用。
    */
    is.setReadListener(new ReadListener() {
      private int totalReadBytes = 0;

      @Override
      public void onDataAvailable() {

        System.out.println("onDataAvailable()--ReadListener thread: " + Thread.currentThread().getName());

        executorService.submit(()->{

          System.out.println("processPool thread: " + Thread.currentThread().getName());

          try {
            byte[] buffer = new byte[1024];
            int readBytes = 0;

            while (is.isReady() && !is.isFinished()) {
              int length = is.read(buffer);

              if (length == -1 && is.isFinished()) {
                System.out.println("Read: " + readBytes + " bytes");
                System.out.println("Total Read: " + totalReadBytes + " bytes");
                return;
              }

              readBytes += length;
              totalReadBytes += length;

            }

            System.out.println("Read: " + readBytes + " bytes");

          } catch (IOException ex) {
            ex.printStackTrace();
            asyncContext.complete();
          }

        });

      }

      // 前端发送的数据接收完成时，执行此方法。
      @Override
      public void onAllDataRead() {

        System.out.println("onAllDataRead()--ReadListener thread: " + Thread.currentThread().getName());

        executorService.submit(()->{

          System.out.println("processPool thread: " + Thread.currentThread().getName());

          try {
            System.out.println("Total Read: " + totalReadBytes + " bytes");
            System.out.println("done!");
            response.setContentType("text/html;charset=UTF-8");
            asyncContext.getResponse().getWriter().println("前端发送的数据读取完成。");
          } catch (IOException ex) {
            ex.printStackTrace();
          }

          // 数据读取完成，结束异步，返回响应信息给前端。
          asyncContext.complete();
        });

      }

      @Override
      public void onError(Throwable t) {
        System.out.println(ExceptionUtils.getStackTrace(t));
        asyncContext.complete();
      }
    });

    long end = System.currentTimeMillis();
    System.out.println("异步IO读取Servlet执行完成，耗时：" + (end - start) + "毫秒");
    System.out.println("退出Servlet线程--" + Thread.currentThread().getName());

  }

  // servlet结束时，清除线程池。
  @SuppressWarnings("Duplicates")
  @Override
  public void destroy() {

    super.destroy();
    executorService.shutdown();
    boolean successfulTerminated = false;
    try {
      successfulTerminated = executorService.awaitTermination(5, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    if (!successfulTerminated) {
      executorService.shutdownNow();
    }
    executorService = null;
  }

}
